#!/usr/bin/env python
#-*- coding:utf-8 -*-

import gtk
import gobject
import cairo
from array import *




class pixel_size:
    w = 24
    h = 24

    font_size = { 1: 20, 2: 16, 3: 10, 4: 8  }




class params:

    # m x n
    m = 0
    n = 0

    # gap
    gap = 0

    # bits
    bits = 0

    # fator de embutimento 'e' = [ (m x n) - gap ] / bits
    @staticmethod
    def e():
        if params.m == 0 or params.n == 0 or params.bits == 0:
            return 0
        else:
            return ((params.m * params.n) - params.gap) / params.bits


    # p(a) = [fator_e x (2a - 1) + 1]/2 + gap
    @staticmethod
    def p(a):
        if params.m == 0 or params.n == 0 or params.bits == 0:
            return 0
        else:
            return ( params.e() * (2*a - 1) + 1 ) / 2 + params.gap




class mensagem:

    texto = ""
    binario = ""
    posicoes = []

    @staticmethod
    def char_to_bin(ch):
        num = ord(ch)
        res = ""
        for bit in range(8):
            mask = 1 << bit
            if num & mask != 0:
                res = "1" + res
            else:
                res = "0" + res
        return res

    @staticmethod
    def binario_from_texto(s):
        resp = ""
        for ch in mensagem.texto:
            resp += mensagem.char_to_bin( ch )
        return resp

    @staticmethod
    def set_posicoes():
        mensagem.posicoes = []
        bits = len( mensagem.binario )
        for bit in range(bits):
            pos = params.p( bit+1 )
            mensagem.posicoes.append( pos )

    @staticmethod
    def get_posicoes_as_str():
        resp = ""
        for p in mensagem.posicoes:
            resp += str(p) + " "
        return resp




class Form:
    def __init__(self):
        b = gtk.Builder()
        b.add_from_file( "Pos.glade" )

        self.window = b.get_object( "window" )
        self.adjustmentX = b.get_object( "adjustmentX" )
        self.adjustmentY = b.get_object( "adjustmentY" )
        self.adjustmentGap = b.get_object( "adjustmentGap" )
        self.txtMensagem = b.get_object( "txtMensagem" )
        self.txtBinario = b.get_object( "txtBinario" )
        self.txtPixels = b.get_object( "txtPixels" )
        self.area = b.get_object( "area" )
        self.btnOpen = b.get_object( "btnOpen" )
        self.checkGrade = b.get_object( "checkGrade" )
        self.checkNumeros = b.get_object( "checkNumeros" )
        self.checkSombreamento = b.get_object( "checkSombreamento" )
        self.labMxN = b.get_object( "labMxN" )
        self.labBits = b.get_object( "labBits" )
        self.labFatorE = b.get_object( "labFatorE" )
        self.pb = None

    def get_img_params(self):
        return \
            int( self.adjustmentX.get_value() ), \
            int( self.adjustmentY.get_value() ), \
            self.pb.get_width() if self.pb else 0, \
            self.pb.get_height() if self.pb else 0

    def get_area_params(self):
        return \
            self.area.get_allocation().width / pixel_size.w, \
            self.area.get_allocation().height / pixel_size.h

    def get_gap(self):
        return int( self.adjustmentGap.get_value() )

    def renova(self):
        self.area.queue_draw()





def main():

    def on_close(sender, event, form):
        gtk.main_quit()

    def on_xy_change(sender, form):
        form.renova()

    def on_gap_change(sender, form):
        atualiza_params(form)

    def on_msg_change(sender, event, form):

        mensagem.texto = form.txtMensagem.get_text()
        mensagem.binario = mensagem.binario_from_texto( mensagem.texto )
        form.txtBinario.set_text( mensagem.binario )

        atualiza_params(form)
        return False

    def on_msg_bin_change(sender, event, form):

        mensagem.texto = ""
        mensagem.binario = form.txtBinario.get_text()
        form.txtMensagem.set_text( "" )

        atualiza_params(form)
        return False

    def on_msg_bin_filter(sender, event, form):

        teclas = ( gtk.keysyms.Left, gtk.keysyms.Right, \
            gtk.keysyms.Home, gtk.keysyms.End, \
            gtk.keysyms.BackSpace, gtk.keysyms.Delete, \
            gtk.keysyms._0, gtk.keysyms._1 )

        if not (event.keyval in teclas):
            print "Tecla invalida no binario: " + gtk.gdk.keyval_name( event.keyval )
            return True

        return False

    def on_open_file(sender, form):
        arq = form.btnOpen.get_filename()
        form.pb = gtk.gdk.pixbuf_new_from_file( arq )

        px, py, pw, ph = form.get_img_params()
        sw, sh = form.get_area_params()

        form.adjustmentX.set_all( value=0, lower=0, upper=pw, \
            step_increment=1, page_increment=10, page_size=0 )
        form.adjustmentY.set_all( value=0, lower=0, upper=ph, \
            step_increment=1, page_increment=10, page_size=0 )

        atualiza_params(form)

    def on_draw(sender, event, form):

        cr = event.window.cairo_create()
        clip = event.area
        cr.rectangle( clip.x, clip.y, clip.width, clip.height )
        cr.clip()

        px, py, pw, ph = form.get_img_params()
        sw, sh = form.get_area_params()

        if pw == 0 or ph == 0:
            return

        if px + sw >= pw:
            sw = pw - px
        if py + sh >= ph:
            sh = ph - py

        if sw <= 0 or sh <= 0:
            return

        pixels = array( "B", form.pb.get_pixels() )
        row = form.pb.get_rowstride()

        e = params.e()

        for x in range(sw):
            for y in range(sh):
                ix = px + x
                iy = py + y
                ipixel = iy * pw + ix + 1

                addr = iy * row + (ix * 3)
                r = pixels[ addr + 0 ]
                g = pixels[ addr + 1 ]
                b = pixels[ addr + 2 ]

                sx = x * pixel_size.w
                sy = y * pixel_size.h

                cr.set_source_rgb( float(r)/255, float(g)/255, float(b)/255 )
                cr.rectangle( sx, sy, pixel_size.w, pixel_size.h )
                cr.fill()

                if ipixel <= params.gap:

                    if form.checkSombreamento.get_active():
                        cr.set_source_rgba( 0.5, 0.5, 0.5, 0.8 )
                        cr.rectangle( sx, sy, pixel_size.w, pixel_size.h )
                        cr.fill()

                else:
                    if e != 0:
                        if (ipixel - params.gap - 1) / e % 2 == 0:
                            cr.set_source_rgba( 0.2, 0.8, 0.5, 0.7 )
                        else:
                            cr.set_source_rgba( 0.8, 0.5, 0.2, 0.7 )

                        if form.checkSombreamento.get_active():
                            cr.rectangle( sx, sy, pixel_size.w, pixel_size.h )
                            cr.fill()

                        if ipixel in mensagem.posicoes:
                            underline = sy + pixel_size.h - 2

                            cr.set_line_width( 2 )

                            cr.set_source_rgb( 0, 0, 0 )
                            cr.move_to( sx+2 + 0.5, underline + 0.5 )
                            cr.line_to( sx + pixel_size.w-2 + 0.5, underline + 0.5 )
                            cr.stroke()

                            cr.set_source_rgb( 1, 1, 1 )
                            cr.move_to( sx+2 + 0.5, underline-1 + 0.5 )
                            cr.line_to( sx + pixel_size.w-2 + 0.5, underline-1 + 0.5 )
                            cr.stroke()


                if form.checkNumeros.get_active():
                    snum = str(ipixel)
                    slen = len(snum)

                    if slen <= 4:

                        # faz um "negativo inteligente" para a cor do texto
                        if r > 180 or r < 80: r = 255 - r
                        else: r = 0

                        if g > 180 or g < 80: g = 255 - g
                        else: g = 0

                        if b > 180 or b < 80: b = 255 - b
                        else: b = 0

                        cr.set_source_rgb( float(r)/255, float(g)/255, float(b)/255 )
                        cr.select_font_face( "Sans", \
                            cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL )
                        cr.set_font_size( pixel_size.font_size[slen] )

                        fx, fy, fw, fh = cr.text_extents( snum )[:4]

                        cr.move_to( sx-fx + 3 + 0.5, sy-fy +3 + 0.5 )
                        cr.show_text( snum )

                if form.checkGrade.get_active():
                    cr.set_line_width( 1 )
                    cr.set_source_rgb( 0, 0, 0 )
                    cr.rectangle( sx + 0.5, sy + 0.5, pixel_size.w, pixel_size.h )
                    cr.stroke()


    def atualiza_params(form):
        px, py, pw, ph = form.get_img_params()
        params.m = pw
        params.n = ph
        params.gap = form.get_gap()
        params.bits = len( mensagem.binario )

        form.labMxN.set_text( str(params.m) + "x" + str(params.n) )
        form.labBits.set_text( str(params.bits) )

        form.labFatorE.set_text( str( params.e() ) )
        if params.e() == 0:
            form.labFatorE.set_markup( "<span foreground='red'><b>0</b> (!)</span>" )


        mensagem.set_posicoes()
        form.txtPixels.set_text( mensagem.get_posicoes_as_str() )

        form.renova()


    form = Form()
    form.checkGrade.set_active(True)
    form.checkNumeros.set_active(True)
    form.checkSombreamento.set_active(True)
    form.adjustmentGap.set_value( 40 )
    form.btnOpen.set_current_folder( "exemplos" )

    form.adjustmentX.connect( "value-changed", on_xy_change, form )
    form.adjustmentY.connect( "value-changed", on_xy_change, form )
    form.adjustmentGap.connect( "value-changed", on_gap_change, form )
    form.window.connect( "delete-event", on_close, form )
    form.btnOpen.connect( "file-set", on_open_file, form )
    form.area.connect( "expose-event", on_draw, form )
    form.checkGrade.connect( "toggled", on_xy_change, form )
    form.checkNumeros.connect( "toggled", on_xy_change, form )
    form.checkSombreamento.connect( "toggled", on_xy_change, form )
    form.txtMensagem.connect( "key-release-event", on_msg_change, form )
    form.txtBinario.connect( "key-press-event", on_msg_bin_filter, form )
    form.txtBinario.connect( "key-release-event", on_msg_bin_change, form )

    atualiza_params(form)

    form.window.show()
    gtk.main()


main()
